로딩 중이에요... 🐣
[코담]
웹개발·실전 프로젝트·AI까지, 파이썬·장고의 모든것을 담아낸 강의와 개발 노트
08 FastAPI 연동 CRUD UI 만들기 frontend | ✅ 편저: 코담 운영자
8강: HTML, Bootstrap, JavaScript로 FastAPI 연동 CRUD UI 만들기
🔗 소스
🎯 강의 목표
- FastAPI 백엔드와 완전히 연동된 CRUD 인터페이스 구축
- HTML + Bootstrap 기반 사용자 인터페이스 구현
- JavaScript로 API 호출 (GET, POST, PUT, DELETE)
🖼️ HTML 레이아웃 개요
🧾 전체 HTML 코드 예시
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS, FastAPI, SQLite로 만드는 CRUD</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center mb-4">JS, FastAPI, SQLite로 만드는 CRUD</h1>
<div class="mb-3">
<button class="btn btn-primary" data-toggle="modal" data-target="#userModal">새 사용자 추가</button>
</div>
<div id="errorLabel" class="alert alert-danger" style="display: none;"></div>
<table id="userTableBody" class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>이름</th>
<th>이메일</th>
<th>작업</th>
</tr>
</thead>
<tbody>
<!-- 여기에 서버에서 불러온 데이터로 테이블이 채워집니다 -->
</tbody>
</table>
<div class="modal fade" id="userModal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="userModalLabel">사용자 추가 / 수정</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="닫기">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form id="userForm">
<div class="form-group">
<label for="userName">이름</label>
<input type="text" class="form-control" id="userName" required>
</div>
<div class="form-group">
<label for="userEmail">이메일</label>
<input type="email" class="form-control" id="userEmail" required>
</div>
<div id="errorMessage" class="alert alert-danger" style="display: none;"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">닫기</button>
<button type="button" class="btn btn-primary" id="saveUserButton">저장</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
🧩 주요 Bootstrap 클래스
클래스명 | 역할 |
---|---|
container mt-5 |
여백 있는 메인 영역 |
btn btn-primary |
파란색 버튼 |
table table-striped |
줄무늬 스타일 테이블 |
modal , modal-dialog |
부트스트랩 모달 구성 |
📋 구조 요약
-
상단 제목:
<h1>
-
사용자 추가 버튼:
#userModal
모달을 열기 위한 트리거 -
사용자 테이블:
#userTableBody
에 데이터가 동적으로 추가됨 -
사용자 입력 모달: 이름, 이메일 입력 후 저장 가능
-
오류 메시지 영역:
#errorLabel
,#errorMessage
🧩 주요 Bootstrap 클래스
클래스명 | 역할 |
---|---|
container mt-5 |
여백 있는 메인 영역 |
btn btn-primary |
파란색 버튼 |
table table-striped |
줄무늬 스타일 테이블 |
modal , modal-dialog |
부트스트랩 모달 구성 |
📋 구조 요약
- 상단 제목:
<h1>
- 사용자 추가 버튼:
#userModal
모달을 열기 위한 트리거 - 사용자 테이블:
#userTableBody
에 데이터가 동적으로 추가됨 - 사용자 입력 모달: 이름, 이메일 입력 후 저장 가능
- 오류 메시지 영역:
#errorLabel
,#errorMessage
🧩 주요 Bootstrap 클래스
클래스명 | 역할 |
---|---|
container mt-5 |
여백 있는 메인 영역 |
btn btn-primary |
파란색 버튼 |
table table-striped |
줄무늬 스타일 테이블 |
modal , modal-dialog |
부트스트랩 모달 구성 |
🧠 JavaScript 기능 전체 코드 및 설명
1. 사용자 목록 조회 - fetchData()
async function fetchData() {
document.getElementById('errorLabel').style.display = 'none';
try {
const response = await fetch('http://localhost:8000/users');
if (!response.ok) throw new Error('네트워크 오류');
const data = await response.json();
const tableBody = document.getElementById('userTableBody');
tableBody.innerHTML = '';
data.forEach(user => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
<td>
<button class="btn btn-warning btn-sm updateButton" data-id="${user.id}">수정</button>
<button class="btn btn-danger btn-sm deleteButton" data-id="${user.id}">삭제</button>
</td>
`;
tableBody.appendChild(row);
});
} catch (error) {
document.getElementById('errorLabel').innerText = '사용자 정보를 불러오지 못했습니다. 나중에 다시 시도해주세요.';
document.getElementById('errorLabel').style.display = 'block';
}
}
- 서버로부터 사용자 목록을 가져와 테이블에 렌더링
- 오류 발생 시 메시지를 화면에 출력
2. 사용자 등록 및 수정 - addUser()
async function addUser() {
const name = document.getElementById('userName').value;
const email = document.getElementById('userEmail').value;
const userId = document.getElementById('saveUserButton').getAttribute('data-id');
const newUser = { name: name, email: email };
let url = 'http://localhost:8000/users';
let method = 'POST';
if (userId) {
url += `/${userId}`;
method = 'PUT';
}
try {
const response = await fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
if (!response.ok) throw new Error('사용자 저장 실패');
$('#userModal').modal('hide');
document.getElementById('userForm').reset();
fetchData();
} catch (error) {
document.getElementById('errorMessage').innerText = error;
document.getElementById('errorMessage').style.display = 'block';
} finally {
document.getElementById('saveUserButton').removeAttribute('data-id');
}
}
- userId 존재 시 수정, 없으면 신규 생성
- 성공 시 모달 닫고 목록 갱신, 실패 시 오류 표시
3. 사용자 정보 로딩 후 모달 열기 - openUpdateModal(userId)
async function openUpdateModal(userId) {
try {
const response = await fetch(`http://localhost:8000/users/${userId}`);
if (!response.ok) throw new Error('사용자 정보를 불러오지 못했습니다.');
const user = await response.json();
document.getElementById('userName').value = user.name;
document.getElementById('userEmail').value = user.email;
document.getElementById('saveUserButton').setAttribute('data-id', user.id);
document.getElementById('userModalLabel').innerText = '사용자 수정';
$('#userModal').modal('show');
} catch (error) {
document.getElementById('errorLabel').innerText = '사용자 정보를 불러오지 못했습니다. 다시 시도해주세요.';
document.getElementById('errorLabel').style.display = 'block';
}
}
- 수정 버튼 클릭 시 사용자 정보 조회 후 모달 열기
4. 사용자 삭제 확인 및 실행 - deleteUser(userId)
async function deleteUser(userId) {
try {
const response = await fetch(`http://localhost:8000/users/${userId}`, {
method: 'DELETE',
});
if (!response.ok) throw new Error('삭제 실패');
fetchData();
} catch (error) {
document.getElementById('errorLabel').innerText = '사용자 삭제 중 오류 발생. 다시 시도해주세요.';
document.getElementById('errorLabel').style.display = 'block';
setTimeout(() => {
document.getElementById('errorLabel').style.display = 'none';
}, 3000);
}
}
- 사용자 삭제 후 테이블 목록 다시 불러옴
5. 삭제 전 확인창 - confirmDelete(userId)
function confirmDelete(userId) {
if (confirm('정말로 이 사용자를 삭제하시겠습니까?')) {
deleteUser(userId);
}
}
- 실제 삭제 실행 전 사용자에게 확인
6. 이벤트 바인딩 - DOMContentLoaded
document.addEventListener("DOMContentLoaded", function() {
document.getElementById('saveUserButton').addEventListener('click', addUser);
document.getElementById('userTableBody').addEventListener('click', function(event) {
if (event.target.classList.contains('updateButton')) {
const userId = event.target.getAttribute('data-id');
openUpdateModal(userId);
}
if (event.target.classList.contains('deleteButton')) {
const userId = event.target.getAttribute('data-id');
confirmDelete(userId);
}
});
fetchData();
});
- 이벤트 초기화 및 첫 데이터 로딩 수행
🔄 프론트와 FastAPI 연동 흐름
📌 API 요청 정리
프론트 동작 | FastAPI URL | 메서드 |
---|---|---|
사용자 전체 조회 | /users |
GET |
사용자 단건 조회 | /users/{id} |
GET |
사용자 생성 | /users |
POST |
사용자 수정 | /users/{id} |
PUT |
사용자 삭제 | /users/{id} |
DELETE |
📌 FastAPI CORS 설정 필요
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
- 브라우저의 CORS 정책으로 인해 백엔드에서 허용 필요
✅ 기능 검증 체크리스트
- [x] 사용자 등록 → 테이블 자동 반영?
- [x] 수정 → 기존 값 모달에 자동 입력?
- [x] 삭제 → 즉시 테이블에서 제거?
- [x] 서버 꺼졌을 때 오류 표시 동작?
🧪 디버깅 팁
console.log()
로 응답 확인- 오류 발생 시
console.error()
활용 - Swagger UI: http://localhost:8000/docs
🏁 마무리
- FastAPI API와 100% 연동된 CRUD 인터페이스 구축
- Bootstrap + JS만으로 관리 시스템 수준의 웹 UI 구현 가능
- 이후 강의에서는 React 기반 SPA로 확장해보는 실습 예정
📌 참고: 본 강의는 FastAPI 학습 시리즈 기반으로 제작되었습니다.